﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace Borg.Framework
{
    public class ScriptRunner
    {
        private readonly LogEventHandler _log;
        private readonly Queue<ScriptEntry> _scriptQueue;
        private readonly Thread _scriptThread;

        public ScriptRunner(LogEventHandler log)
        {
            _scriptQueue = new Queue<ScriptEntry>();
            _scriptThread = null;
            _log = log;
        }

        public ScriptRunner(IScriptExecutor scriptExecutor, LogEventHandler logHandler)
        {
            _scriptQueue = new Queue<ScriptEntry>();
            _scriptThread = null;
            _log = logHandler;
            ScriptExecutor = scriptExecutor;
            _scriptThread = new Thread(RunScriptsThread);
        }

        public void ExecuteScript(string script)
        {
            var identifier = script;

            if (identifier.Length > 40)
                identifier = identifier.Substring(0, 0x25) + "...";

            InternalExecuteScript(identifier, script);
        }

        public void ExecuteScriptFile(string path)
        {
            if (!File.Exists(path))
                LogEvent(
                    MessageType.Error,
                    "Error on executing script file: \"{0}\"\n  Script file not found",
                    new object[] {path});
            else
            {
                string str;

                try
                {
                    str = File.ReadAllText(path);
                }
                catch (Exception)
                {
                    LogEvent(
                        MessageType.Error,
                        "Unknown error on reading script file: \"{0}\"",
                        new object[] {path});

                    return;
                }

                if (str.Length == 0)
                    LogEvent(
                        MessageType.Warning,
                        "Warning on executing script file: {0}\n  Script file is empty",
                        new object[] {path});

                else if (ScriptExecutor == null)
                    LogEvent(
                        MessageType.Error,
                        "Error on executing script file: \"{0}\"\n  Script executor is missing",
                        new object[] {path});
                else
                    InternalExecuteScript(path, str);
            }
        }

        public void ExecuteScriptResource(PluginInfo pluginInfo, string scriptName)
        {
            using (var getter = new ResourceStreamGetter(pluginInfo, scriptName, false))
            {
                using (var reader = new StreamReader(getter.Stream))
                {
                    try
                    {
                        var script = reader.ReadToEnd();

                        if (ScriptExecutor == null)
                            LogEvent(
                                MessageType.Error,
                                "Error on executing script resource: \"{0}-{1}\"\n  Script executor is missing",
                                new object[] {pluginInfo, scriptName});
                        else
                            InternalExecuteScript(scriptName, script);
                    }
                    catch (Exception exception)
                    {
                        LogEvent(
                            MessageType.Error,
                            "Unknown error on reading script resource: \"{0}-{1}\"\n  {2}",
                            new object[] {pluginInfo.Name, scriptName, exception.Message});
                    }
                }
            }
        }

        public void ExecuteStartupScripts()
        {
            if (!Runtime.Arguments.ContainsKey("scripts"))
                LogEvent(
                    MessageType.Error,
                    "Error on executing startup scripts:\n" +
                    "  Script argument not found." +
                    " Please use \"/args:scripts=<path1>[,<path2>,...]\" argument for startup",
                    new object[0]);
            else
            {
                var str = Runtime.Arguments["scripts"];

                if (string.IsNullOrEmpty(str))
                    LogEvent(
                        MessageType.Error,
                        "Error on executing startup scripts:\n  Script argument must not be empty",
                        new object[0]);
                else
                {
                    var strArray = str.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);

                    foreach (var str2 in strArray)
                        ExecuteScriptFile(str2);
                }
            }
        }

        private void InternalExecuteScript(string identifier, string script)
        {
            if (ScriptExecutor == null)
                LogEvent(
                    MessageType.Error,
                    "Error on executing script: Script executor is missing",
                    new object[0]);

            else if (Runtime.IsRuntimeThread())
            {
                LogEvent(MessageType.Info, "Enqueuing: " + identifier, new object[0]);
                
                lock (_scriptQueue)
                {
                    _scriptQueue.Enqueue(new ScriptEntry(identifier, script));
                    
                    if (!_scriptThread.IsAlive)
                    {
                        LogEvent(MessageType.Info, "Starting script thread.", new object[0]);
                        _scriptThread.Start();
                    }
                }
            }
            else
            {
                LogEvent(MessageType.Info, "Executing immediate: " + identifier, new object[0]);
                LogScriptResult(ScriptExecutor.ExecuteSync(script));
            }
        }

        private void LogEvent(MessageType type, string message, params object[] args)
        {
            _log(this, new LogEventArgs(type, VerbosityLevel.Normal, message, args));
        }

        private void LogScriptResult(IScriptResult res)
        {
            LogEvent(
                MessageType.Info,
                "Script Result: StateMessage: {0}, Errors: {1}.",
                new object[] { res.StateMessage, res.Errors.Length });

            if (res.Errors.Length > 0 || res.State != CommandState.Completed)
            { }
        }

        private void RunScriptsThread()
        {
            var flag = true;

            do
            {
                ScriptEntry entry;

                lock (_scriptQueue)
                {
                    entry = _scriptQueue.Dequeue();

                    if (_scriptQueue.Count == 0)
                        flag = false;
                }

                LogEvent(MessageType.Info, "Executing (ScriptThread): " +
                    entry.Identifier, new object[0]);

                var res = ScriptExecutor.ExecuteSync(entry.Script);
                LogScriptResult(res);
            }

            while (flag);
            LogEvent(MessageType.Info, "Script thread executed all queued scripts.", new object[0]);
        }

        public IScriptExecutor ScriptExecutor { get; set; }

        private class ScriptEntry
        {
            public readonly string Identifier;
            public readonly string Script;

            public ScriptEntry(string identifier, string script)
            {
                Identifier = identifier;
                Script = script;
            }
        }
    }
}

